# coding = utf-8
# File date: Hi_2022/5/30 21:54
# File_name: CrazyClick.py
import sys
from threading import Thread
from time import sleep
from tkinter import Tk, IntVar, StringVar
from tkinter.ttk import Frame, Label, Button, Entry, Radiobutton
import pynput
from pynput.mouse import Button as mouse_but
from loguru import logger


class Clicker:
    def __init__(self, width=410, height=100):
        # 配置日志
        log_format = "<level> {time:YYYY-MM-DD HH:mm:ss.ms} | level:{level} | ThreadID:{thread} | line{line} | {message} </level>"
        logger.remove()  # 先删除默认样式
        logger.add(sys.stdout, level=5, format=log_format, colorize=True)  # 输出到控制台,并设置输出最低为5，自定义日志颜色

        # 根窗口
        self.master = Tk()
        logger.success("窗口初始化")
        self.master.title("点击器")

        # 桌面长宽
        self.win_w = self.master.winfo_screenwidth()
        self.win_h = self.master.winfo_screenheight()

        # 设置窗口位置和长宽
        self.master.geometry(f"{width}x{height}+{(self.win_w - width) // 2}+{(self.win_h - height) // 2}")
        logger.success(f"设置桌面长宽{width}x{height}并居中")

        # 不可调整大小
        self.master.resizable(False, False)
        logger.success("设置窗口不可调整大小")

        # 窗口置顶
        self.master.wm_attributes('-topmost', 1)
        logger.success("设置窗口置顶")

        # 鼠标、键盘实例化
        self.mouse: pynput.mouse.Controller = pynput.mouse.Controller()
        logger.success("鼠标实例化")
        self.keyboard: pynput.keyboard.Controller = pynput.keyboard.Controller()
        logger.success("键盘实例化")

        # 点击中参数
        self.clicks_states = False

        # 点击参数初始化
        self.clicks = IntVar()  # 点击次数,方便强制停止
        self.clicks.set(1)
        self.click_duration = IntVar()  # 点击持续时长
        self.click_duration.set(20)
        self.click_interval = IntVar()  # 点击间隔
        self.click_interval.set(50)
        self.click_button = StringVar()  # 鼠标默认点击按键
        self.click_button.set("left")

        # 创建上下两个frame
        self.up_fra = Frame(self.master)
        self.down_fra = Frame(self.master)
        self.up_fra.place(relx=0, rely=0, relwidth=1, relheight=0.5)
        self.down_fra.place(relx=0, rely=0.5, relwidth=1, relheight=0.5)

        # 独立线程系统热键监控F10
        listener_keyboard_thred = Thread(target=self.listener_keyboard)
        listener_keyboard_thred.setDaemon(True)
        listener_keyboard_thred.start()
        logger.success("开启键盘监控")

        # 所有输入框校验方法
        self.entry_validate = self.master.register(Clicker.check_digit)

        # 初始化方法
        self.click_parameters()

        # 窗口关闭回调
        self.master.protocol('WM_DELETE_WINDOW', self.root_close)

        # 主循环
        logger.success("开启主循环")
        self.master.mainloop()

    def click_parameters(self):
        # 点击次数
        click_count_label = Label(self.up_fra, text="次数:")
        click_count_entry = Entry(self.up_fra, textvariable=self.clicks, width=8, validate='key')
        click_count_entry.config(validatecommand=(self.entry_validate, '%P'))
        logger.success("点击次数布局和校验")

        # 点击持续时长
        single_click_duration_label = Label(self.up_fra, text="单击持续(ms):")  # 点击持续时长文本
        single_click_duration_entry = Entry(self.up_fra, textvariable=self.click_duration, width=7, validate='key')
        single_click_duration_entry.config(validatecommand=(self.entry_validate, '%P'))
        logger.success("点击持续时长布局和校验")

        # 两次点击间隔时长
        between_two_clicks_label = Label(self.up_fra, text="点击间隔(ms)：")  # 两次点击间隔时长文本
        between_two_clicks_entry = Entry(self.up_fra, textvariable=self.click_interval, width=7, validate='key')
        between_two_clicks_entry.config(validatecommand=(self.entry_validate, '%P'))
        logger.success("两次点击间隔时长布局和校验")

        # 鼠标键位单选
        mouse_left = Radiobutton(self.down_fra, text='左', variable=self.click_button, value="left")
        mouse_right = Radiobutton(self.down_fra, text='右', variable=self.click_button, value="right")
        mouse_middle = Radiobutton(self.down_fra, text='中', variable=self.click_button, value="middle")

        # 开始停止按钮,绑定独立线程回调不阻塞主线程
        click_but = Button(self.down_fra, text="开始/停止 [F10]", command=self.click_but_thred)
        logger.success("开始停止按钮布局和绑定回调方法")

        # 上层放置
        click_count_label.place(relx=0.02, rely=0.3, relwidth=0.15, relheight=0.55)
        click_count_entry.place(relx=0.1, rely=0.3, relwidth=0.15, relheight=0.55)
        single_click_duration_label.place(relx=0.29, rely=0.3, relwidth=0.2, relheight=0.55)
        single_click_duration_entry.place(relx=0.49, rely=0.3, relwidth=0.13, relheight=0.55)
        between_two_clicks_label.place(relx=0.65, rely=0.3, relwidth=0.2, relheight=0.55)
        between_two_clicks_entry.place(relx=0.85, rely=0.3, relwidth=0.13, relheight=0.55)
        logger.success("上层布局完成")

        # 下层放置
        mouse_left.place(relx=0.1, rely=0.2, relwidth=0.15, relheight=0.55)
        mouse_right.place(relx=0.27, rely=0.2, relwidth=0.15, relheight=0.55)
        mouse_middle.place(relx=0.44, rely=0.2, relwidth=0.15, relheight=0.55)
        click_but.place(relx=0.6, rely=0.2, relwidth=0.31, relheight=0.55)
        logger.success("下层布局完成")

    def click_but_thred(self):
        """开始停止按钮调用，创建独立线程执行点击停止"""
        if not self.clicks_states:  # 执行点击
            if int(self.clicks.get()) == 0:
                logger.warning(f"设置点击0次不执行")
                return

            # 获取点击参数
            logger.success("获取点击参数")
            click_button = self.check_button(self.click_button)
            click_duration = self.click_duration.get()
            click_interval = self.click_interval.get()
            click_times = self.clicks.get()

            logger.success(f"点击键位:{click_button},单击持续:{click_duration},单击间隔:{click_interval},点击次数:{click_times}")

            logger.success("独立线程开始/停止点击")
            args = (click_button, click_duration, click_interval, click_times)
            click_thred = Thread(target=self.auto_click, args=args)
            click_thred.setDaemon(True)
            click_thred.start()

        elif self.clicks_states:  # 停止点击
            self.clicks_states = False
            logger.success(f"强制提前终止点击,设置clicks_states为False")

    def auto_click(self, click_button: mouse_but, click_duration: int, click_interval: int, click_times: int):
        """
        :param click_button: 点击按钮
        :param click_duration: 单击点击持续时间
        :param click_interval: 单击间隔
        :param click_times: 点击次数
        :return:
        """
        self.clicks_states = True
        logger.success(f"设置clicking参数:{self.clicks_states}")

        logger.success("2秒后开始点击")
        sleep(2)

        for i in range(click_times):
            if not self.clicks_states:
                logger.warning(f"clicks_states当前状态:{self.clicks_states},被迫提前终止点击")
                self.clicks.set(0)
                logger.warning("强制提前终止点击,设置点击次数0")
                break

            self.mouse.press(click_button)  # 按下鼠标
            logger.info(f"{self.click_button.get()}键按下--{i + 1}")

            sleep(int(click_duration) / 1000)

            self.mouse.release(click_button)  # 释放按键
            logger.info(f"{self.click_button.get()}键释放--{i + 1}")

            sleep(click_interval / 1000)  # 休眠间隔等待下一次点击

            self.clicks.set(click_times - i - 1)
        self.clicks_states = False
        logger.success(f"点击完成，设置clicking参数:{self.clicks_states}")

    @staticmethod
    def check_button(check_but: StringVar):
        but_str = check_but.get()
        # 确定按键转换
        if but_str == "left":
            mouse_button = mouse_but.left
        elif but_str == "right":
            mouse_button = mouse_but.right
        elif but_str == "middle":
            mouse_button = mouse_but.middle
        else:
            mouse_button = mouse_but.left
        return mouse_button

    def listener_keyboard(self):
        def on_press(key):
            """按下按键时执行。"""
            logger.info(f"{key}按下")

        def on_release(key):
            """松开按键时执行。"""
            logger.info(f"{key}释放")
            if key == pynput.keyboard.Key.f10:  # 按下F10回调开始/停止
                logger.success(f"{key}释放,开始/停止点击")
                self.click_but_thred()

        # 注册系统全局热键监控键盘，监控按下F10
        with pynput.keyboard.Listener(on_press=on_press, on_release=on_release) as listener:  # 收集事件直到发布
            listener.join()

    def root_close(self):
        """将全局键盘监控状态参数改为False,并自动点击两次shift自动结束键盘监控"""
        self.clicks_states = False
        logger.error(f"窗口即将关闭,更新clicks_states状态:{self.clicks_states}")
        self.master.destroy()

    @staticmethod
    def check_digit(content):
        try:
            content = int(content)
        except ValueError:
            logger.success(f"校验{content}:不能转换为数字")
            return False
        if isinstance(content, int) and content >= 0:  # 判断是否十进制
            logger.success(f"校验{content}:是十进制且大于等于0")
            return True
        else:
            logger.success(f"校验{content}:不是十进制或小于0")
            return False


if __name__ == '__main__':
    Clicker()
